<?php

// 引入配置文件
require_once 'WxPay.Config.php';

// 微信支付数据基类
class WxPayDataBase
{
    protected $values = array();

    /**
     * 设置某个字段的值
     * @param string $key 字段名
     * @param mixed $value 字段值
     */
    public function Set($key, $value)
    {
        $this->values[$key] = $value;
    }

    /**
     * 获取某个字段的值
     * @param string $key 字段名
     * @return mixed 字段值，如果不存在则返回null
     */
    public function Get($key)
    {
        return array_key_exists($key, $this->values) ? $this->values[$key] : null;
    }

    /**
     * 判断某个字段是否已设置
     * @param string $key 字段名
     * @return bool 如果已设置返回true，否则返回false
     */
    public function IsSet($key)
    {
        return array_key_exists($key, $this->values);
    }

    /**
     * 将数据对象转换为数组
     * @return array 数据数组
     */
    public function ToArray()
    {
        return $this->values;
    }

    /**
     * 生成签名
     * @return string 签名结果
     */
    public function MakeSign()
    {
        // 按字典序排序参数
        ksort($this->values);
        $string = $this->ToUrlParams();
        // 拼接API密钥
        $string = $string . "&key=" . WxPayConfig::KEY;
        // MD5加密
        $string = md5($string);
        // 转换为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @return string 拼接后的字符串
     */
    public function ToUrlParams()
    {
        $buff = "";
        foreach ($this->values as $k => $v) {
            if ($k != "sign" && $v != "" && !is_array($v)) {
                $buff .= $k . "=" . $v . "&";
            }
        }
        $buff = trim($buff, "&");
        return $buff;
    }

    /**
     * 将数组转换为XML
     * @return string XML字符串
     * @throws WxPayException 如果数组为空则抛出异常
     */
    public function ToXml()
    {
        if (empty($this->values)) {
            throw new WxPayException("数组数据为空！");
        }
        $xml = "<xml>";
        foreach ($this->values as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 将XML转换为数组
     * @param string $xml XML字符串
     * @return array 转换后的数组
     */
    public static function FromXml($xml)
    {
        if (!$xml) {
            throw new WxPayException("xml数据异常！");
        }
        // 禁止引用外部xml实体
        libxml_set_external_entity_loader(function ($public, $system, $context) {
            return false; // 拒绝所有外部实体
        });
        //libxml_set_external_entity_loader();
        $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $values;
    }

    /**
     * 检查签名是否正确
     * @return bool 如果签名正确返回true，否则返回false
     */
    public function CheckSign()
    {
        if (!array_key_exists("sign", $this->values)) {
            return false;
        }
        $sign = $this->values['sign'];
        unset($this->values['sign']);
        $mySign = $this->MakeSign();
        if ($sign == $mySign) {
            return true;
        }
        return false;
    }
}

// 统一支付接口输入数据类
class WxPayUnifiedOrder extends WxPayDataBase
{
    /**
     * 设置商品或支付单简要描述
     * @param string $value 商品或支付单简要描述
     */
    public function SetBody($value)
    {
        $this->Set('body', $value);
    }

    /**
     * 获取商品或支付单简要描述
     * @return string 商品或支付单简要描述
     */
    public function GetBody()
    {
        return $this->Get('body');
    }

    public function SetOut_trade_no($value)
    {
        $this->Set('out_trade_no', $value);
    }

    public function IsOut_trade_noSet()
    {
        return $this->IsSet('out_trade_no');
    }



    public function IsTotal_feeSet()
    {
        return $this->IsSet('total_fee');
    }

    public function IsOpenidSet()
    {
        return $this->IsSet('openid');
    }

    public function SetSign()
    {
        $sign = $this->MakeSign();
        $this->Set('sign', $sign);
    }

    public function IsProduct_idSet()
    {
        return $this->IsSet('product_id');
    }

    public function IsTrade_typeSet()
    {
        return $this->IsSet('trade_type');
    }

    public function IsNotify_urlSet()
    {
        return $this->IsSet('notify_url');
    }

    public function SetAppid($value)
    {
        $this->Set('appid', $value);
    }

    public function SetMch_id($value)
    {
        $this->Set('mch_id', $value);
    }

    public function SetNonce_str($value)
    {
        $this->Set('nonce_str', $value);
    }



    public function ToXml()
    {
        if ($this->values['trade_type'] == 'NATIVE' && !$this->IsSet('product_id')) {
            throw new WxPayException("当trade_type是NATIVE时，product_id为必填参数！");
        }
        return parent::ToXml();
    }


    public function GetTrade_type(){
        return $this->Get('trade_type');
    }

    public function GetOut_trade_no()
    {
        return $this->Get('out_trade_no');
    }

    public function SetTotal_fee($value)
    {
        $this->Set('total_fee', $value);
    }

    public function SetNotify_url($value)
    {
        $this->Set('notify_url', $value);
    }

    public function SetTrade_type($value)
    {
        $this->Set('trade_type', $value);
    }

    public function SetProduct_id($value)
    {
        $this->Set('product_id', $value);
    }

    /**
     * 判断商品或支付单简要描述是否已设置
     * @return bool 如果已设置返回true，否则返回false
     */
    public function IsBodySet()
    {
        return $this->IsSet('body');
    }

    // 其他统一支付接口所需字段的设置和获取方法可按需添加
}

// 微信支付结果类
class WxPayResults extends WxPayDataBase
{
    /**
     * 初始化结果数据
     * @param string $xml XML字符串
     * @return array 解析后的结果数组
     * @throws WxPayException 如果XML解析失败或签名验证失败则抛出异常
     */
    public static function Init($xml)
    {
        $obj = new self();
        $obj->FromXml($xml);
        if ($obj->CheckSign() == false) {
            throw new WxPayException("签名验证错误！");
        }
        return $obj->ToArray();
    }
}

// 微信支付异常类
class WxPayException extends Exception
{
    public function errorMessage()
    {
        return $this->getMessage();
    }
}    